#!/bin/sh

# Copyright (c) 2016-2018 Mark Allen
# Copyright (c) 2011, 2012 Spawngrid, Inc
# Copyright (c) 2011 Evax Software <contact(at)evax(dot)org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

unset ERL_TOP

# Make sure CDPATH doesn't affect cd in case path is relative.
unset CDPATH

KERL_VERSION='2.2.1'

DOCSH_GITHUB_URL='https://github.com/erszcz/docsh.git'
ERLANG_DOWNLOAD_URL='https://erlang.org/download'
KERL_CONFIG_STORAGE_FILENAME='.kerl_config'

TMP_DIR=${TMP_DIR:-'/tmp'}
if [ -z "$HOME" ]; then
    # shellcheck disable=SC2016
    echo 'Error: $HOME is empty or not set.' 1>&2
    exit 1
fi

# Default values
OTP_GITHUB_URL=${OTP_GITHUB_URL:='https://github.com/erlang/otp'}
KERL_BASE_DIR=${KERL_BASE_DIR:="$HOME"/.kerl}
KERL_CONFIG=${KERL_CONFIG:="$HOME"/.kerlrc}
KERL_DOWNLOAD_DIR=${KERL_DOWNLOAD_DIR:="${KERL_BASE_DIR:?}"/archives}
KERL_BUILD_DIR=${KERL_BUILD_DIR:="${KERL_BASE_DIR:?}"/builds}
KERL_GIT_DIR=${KERL_GIT_DIR:="${KERL_BASE_DIR:?}"/gits}
KERL_DOC_TARGETS=${KERL_DOC_TARGETS:="chunks"}

if [ -n "$OTP_GITHUB_URL" ]; then
    _OGU="$OTP_GITHUB_URL"
fi
if [ -n "$KERL_CONFIGURE_OPTIONS" ]; then
    _KCO="$KERL_CONFIGURE_OPTIONS"
fi
if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
    _KCA="$KERL_CONFIGURE_APPLICATIONS"
fi
if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
    _KCDA="$KERL_CONFIGURE_DISABLE_APPLICATIONS"
fi
if [ -n "$KERL_SASL_STARTUP" ]; then
    _KSS="$KERL_SASL_STARTUP"
fi
if [ -n "$KERL_DEPLOY_SSH_OPTIONS" ]; then
    _KDSSH="$KERL_DEPLOY_SSH_OPTIONS"
fi
if [ -n "$KERL_DEPLOY_RSYNC_OPTIONS" ]; then
    _KDRSYNC="$KERL_DEPLOY_RSYNC_OPTIONS"
fi
if [ -n "$KERL_INSTALL_MANPAGES" ]; then
    _KIM="$KERL_INSTALL_MANPAGES"
fi
if [ -n "$KERL_INSTALL_HTMLDOCS" ]; then
    _KIHD="$KERL_INSTALL_HTMLDOCS"
fi
if [ -n "$KERL_BUILD_PLT" ]; then
    _KBPLT="$KERL_BUILD_PLT"
fi
if [ -n "$KERL_BUILD_DOCS" ]; then
    _KBD="$KERL_BUILD_DOCS"
fi
if [ -n "$KERL_DOC_TARGETS" ]; then
    _KDT="$KERL_DOC_TARGETS"
fi
if [ -n "$KERL_BUILD_BACKEND" ]; then
    _KBB="$KERL_BUILD_BACKEND"
fi

OTP_GITHUB_URL=
KERL_CONFIGURE_OPTIONS=
KERL_CONFIGURE_APPLICATIONS=
KERL_CONFIGURE_DISABLE_APPLICATIONS=
KERL_SASL_STARTUP=
KERL_DEPLOY_SSH_OPTIONS=
KERL_DEPLOY_RSYNC_OPTIONS=
KERL_INSTALL_MANPAGES=
KERL_INSTALL_HTMLDOCS=
KERL_BUILD_PLT=
KERL_BUILD_DOCS=
KERL_DOC_TARGETS=
KERL_BUILD_BACKEND=

# ensure the base dir exists
mkdir -p "$KERL_BASE_DIR" || exit 1

# source the config file if available
if [ -f "$KERL_CONFIG" ]; then
    # shellcheck source=/dev/null
    . "$KERL_CONFIG"
fi

if [ -n "$_OGU" ]; then
    OTP_GITHUB_URL="$_OGU"
fi
if [ -n "$_KCO" ]; then
    KERL_CONFIGURE_OPTIONS="$_KCO"
fi
if [ -n "$_KCA" ]; then
    KERL_CONFIGURE_APPLICATIONS="$_KCA"
fi
if [ -n "$_KCDA" ]; then
    KERL_CONFIGURE_DISABLE_APPLICATIONS="$_KCDA"
fi
if [ -n "$_KSS" ]; then
    KERL_SASL_STARTUP="$_KSS"
fi
if [ -n "$_KDSSH" ]; then
    KERL_DEPLOY_SSH_OPTIONS="$_KDSSH"
fi
if [ -n "$_KDRSYNC" ]; then
    KERL_DEPLOY_RSYNC_OPTIONS="$_KDRSYNC"
fi
if [ -n "$_KIM" ]; then
    KERL_INSTALL_MANPAGES="$_KIM"
fi
if [ -n "$_KIHD" ]; then
    KERL_INSTALL_HTMLDOCS="$_KIHD"
fi
if [ -n "$_KBPLT" ]; then
    KERL_BUILD_PLT="$_KBPLT"
fi
if [ -n "$_KBD" ]; then
    KERL_BUILD_DOCS="$_KBD"
fi
if [ -n "$_KDT" ]; then
    KERL_DOC_TARGETS="$_KDT"
fi
if [ -n "$_KBB" ]; then
    KERL_BUILD_BACKEND="$_KBB"
fi

if [ -z "$KERL_SASL_STARTUP" ]; then
    INSTALL_OPT='-minimal'
else
    INSTALL_OPT='-sasl'
fi

if [ -z "$KERL_BUILD_BACKEND" ]; then
    KERL_BUILD_BACKEND='git'
fi
if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
    KERL_USE_AUTOCONF=1
elif [ "$KERL_BUILD_BACKEND" != 'tarball' ]; then
    echo "Unhandled value KERL_BUILD_BACKEND='${KERL_BUILD_BACKEND}'"
    echo "KERL_BUILD_BACKEND must be one of 'git' (default) or 'tarball'"
    exit 1
fi

KERL_SYSTEM=$(uname -s)
case "$KERL_SYSTEM" in
    Darwin|FreeBSD|OpenBSD)
        MD5SUM='openssl md5'
        MD5SUM_FIELD=2
        SED_OPT=-E
        CP_OPT=-a
        ;;
    *)
        MD5SUM=md5sum
        MD5SUM_FIELD=1
        SED_OPT=-r
        CP_OPT=-pr
        ;;
esac


usage() {
    echo 'kerl: build and install Erlang/OTP'
    echo "usage: $0 <command> [options ...]"
    printf '\n  <command>       Command to be executed\n\n'
    echo 'Valid commands are:'
    echo '  build           Build specified release or git repository'
    echo '  install         Install the specified release at the given location'
    echo '  deploy          Deploy the specified installation to the given host and location'
    echo '  update          Update the list of available releases from your source provider'
    echo '  list            List releases, builds and installations'
    echo '  delete          Delete builds and installations'
    echo '  install-docsh   Install erl shell documentation access extension - docsh'
    echo '  path            Print the path of a given installation'
    echo '  active          Print the path of the active installation'
    echo '  plt             Print Dialyzer PLT path for the active installation'
    echo '  status          Print available builds and installations'
    echo '  prompt          Print a string suitable for insertion in prompt'
    echo '  cleanup         Remove compilation artifacts (use after installation)'
    echo "  version         Print current version (current: $KERL_VERSION)"
    exit 1
}

if [ $# -eq 0 ]; then usage; fi

get_releases() {
    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
        get_git_releases
    else
        get_tarball_releases
    fi
}

get_git_releases() {
    git ls-remote --tags --refs "$OTP_GITHUB_URL" \
    | cut -f2 \
    | cut -d'/' -f3- \
    | sed $SED_OPT \
        -e '# Delete all tags starting with ":" as to not mix' \
        -e '# them with the prefixed lines we`re generating next.' \
        -e '/^:/d' \
        \
        -e '# Prefix "OTP*" release lines with the crux of their versions.' \
        -e '#  - "OTP_R16B01_RC1"  =>  ":16B01_RC1 OTP_R16B01_RC1"' \
        -e '#  - "OTP_R16B03"      =>  ":16B03 OTP_R16B03"' \
        -e '#  - "OTP-17.0"        =>  ":17.0 OTP-17.0"' \
        -e '#  - "OTP-17.3.1"      =>  ":17.3.1 OTP-17.3.1"' \
        -e '#  - "OTP-19.0-rc1"    =>  ":19.0-rc1 OTP-19.0-rc1"' \
        -e '#  - "OTP-19.0-rc2"    =>  ":19.0-rc2 OTP-19.0-rc2"' \
        -e 's/^(OTP[-_](R?([0-9][^ :]*).*))/:\3 \2/' \
        \
        -e '# Delete all lines that didn`t get prefixed above.' \
        -e '/^[^:]/d' \
        \
        -e '# Move the colon markers preceding each version prefix' \
        -e '# as to preceed the tag suffix instead, which will make' \
        -e '# throwing the version prefixes easier later on.' \
        -e '#  - ":16B01_RC1 OTP_R16B03"  =>  "16B01_RC1 :OTP_R16B01_RC1"' \
        -e '#  -     ":16B03 OTP_R16B03"  =>  "16B03 :OTP_R16B03"' \
        -e '#  -      ":17.0 OTP_R16B03"  =>  "17.0 :OTP-17.0"' \
        -e '#  -    ":17.3.1 OTP_R16B03"  =>  "17.3.1 :OTP-17.3.1"' \
        -e '#  -  ":19.0-rc1 OTP_R16B03"  =>  "19.0-rc1 :OTP-19.0-rc1"' \
        -e '#  -  ":19.0-rc2 OTP_R16B03"  =>  "19.0-rc2 :OTP-19.0-rc2"' \
        -e 's/^:([^ ]+) /\1 :/' \
        \
        -e '# Repeatedly replace sequences of one or more dots, dashes' \
        -e '# or underscores, within each version prefix, with single' \
        -e '# space characters.' \
        -e '#  - "16B01_RC1 :OTP_R16B01_RC1"  =>  "16B01 RC1 :OTP_R16B01_RC1"' \
        -e '#  -     "16B03 :OTP_R16B03"      =>  "16B03 :OTP_R16B03"' \
        -e '#  -      "17.0 :OTP-17.0"        =>  "17 0 :OTP-17.0"' \
        -e '#  -    "17.3.1 :OTP-17.3.1"      =>  "17 3 1 :OTP-17.3.1"' \
        -e '#  -  "19.0-rc1 :OTP-19.0-rc1"    =>  "19 0 rc1 :OTP-19.0-rc1"' \
        -e '#  -  "19.0-rc2 :OTP-19.0-rc2"    =>  "19 0 rc2 :OTP-19.0-rc2"' \
        -e ':loop' \
           -e 's/^([^:]*)[.-]+([^:]*) :/\1 \2 :/' \
        -e 't loop' \
        \
        -e '# Repeatedly replace "A", "B", or "C" separators, within each' \
        -e '# version prefix, with " 0 ", " 1 " and " 2 ", respectively.' \
        -e '#  - "16B01 RC1 :OTP_R16B01_RC1"  =>  "16 1 01 RC1 :OTP_R16B01_RC1"' \
        -e '#  -     "16B03 :OTP_R16B03"      =>  "16 1 03 :OTP_R16B03"' \
        -e ':loop2' \
            -e 's/^(.*[0-9]+)A([^:]*) :/\1 0 \2 :/' \
            -e 's/^(.*[0-9]+)B([^:]*) :/\1 1 \2 :/' \
            -e 's/^(.*[0-9]+)C([^:]*) :/\1 2 \2 :/' \
        -e 't loop2' \
        \
        -e '# Repeatedly replace space-release candidate infixes, within' \
        -e '# each version prefix, with a leading zero followed by' \
        -e '# the candidate number.' \
        -e '# - "16 1 01 RC1 :OTP_R16B01_RC1"  =>  "16 1 01 0 1 :OTP_R16B01_RC1"' \
        -e '# -      "19 0 rc1 :OTP-19.0-rc1"  =>  "19 0 0 1 :OTP-19.0-rc1"' \
        -e '# -      "19 0 rc2 :OTP-19.0-rc2"  =>  "19 0 0 2 :OTP-19.0-rc2"' \
        -e ':loop3' \
            -e 's/^([^:]* )(rc|RC)([0-9]+)(( [^:]*)?) :/\10 \3\4 :/' \
        -e 't loop3' \
        \
        -e '# Repeatedly prefix single digits, within each version prefix,' \
        -e '# with leading zeroes.' \
        -e '#  - "16 1 01 0 1 :OTP_R16B01_RC1"  =>  "16 01 01 00 01 :OTP_R16B01_RC1"' \
        -e '#  -     "16 1 03 :OTP_R16B03"      =>  "16 01 03 :OTP_R16B03"' \
        -e '#  -        "17 0 :OTP-17.0"        =>  "17 00 :OTP-17.0"' \
        -e '#  -      "17 3 1 :OTP-17.3.1"      =>  "17 03 01 :OTP-17.3.1"' \
        -e '#  -    "19 0 0 1 :OTP-19.0-rc1"    =>  "19 00 00 01 :OTP-19.0-rc.1"' \
        -e '#  -    "19 0 0 2 :OTP-19.0-rc2"    =>  "19 00 00 02 :OTP-19.0-rc.2"' \
        -e ':loop4' \
            -e 's/^([^:]*[^0-9:])([0-9])(([^0-9][^:]*)?) :/\10\2\3 :/' \
        -e 't loop4' \
        \
        -e '# Suffix each version prefix with 00 as to not compare ':' with a number.' \
        -e '#  - "16 01 01 00 01 :OTP_R16B01_RC1"  =>  "16 01 01 00 01 00 :OTP_R16B01_RC1"' \
        -e '#  -       "16 01 03 :OTP_R16B03"      =>  "16 01 03 00 :OTP_R16B03"' \
        -e '#  -          "17 00 :OTP-17.0""       =>  "17 00 00 :OTP-17.0"' \
        -e '#  -       "17 03 01 :OTP-17.3.1"      =>  "17 03 01 00 :OTP-17.3.1"' \
        -e '#  -    "19 00 00 01 :OTP-19.0-rc.1"   =>  "19 00 00 01 00 :OTP-19.0-rc.1"' \
        -e '#  -    "19 00 00 02 :OTP-19.0-rc.2"   =>  "19 00 00 02 00 :OTP-19.0-rc.2"' \
        -e 's/^([^:]+) :/\1 00 :/' \
        \
     | LC_ALL=C sort -n \
     | cut -d':' -f2-
}

get_tarball_releases() {
    tmp="$(mktemp "$TMP_DIR"/kerl.XXXXXX)"
    if [ 200 = "$(\curl -qsL --output "$tmp" --write-out '%{http_code}' $ERLANG_DOWNLOAD_URL/)" ]; then
        sed $SED_OPT \
            -e 's/^.*<[aA] [hH][rR][eE][fF]=\"otp_src_([-0-9A-Za-z_.]+)\.tar\.gz\">.*$/\1/' \
            -e '/^R1|^[0-9]/!d' "$tmp" \
        | sed -e 's/^R\(.*\)/\1:R\1/' \
        | sed -e 's/^\([^\:]*\)$/\1-z:\1/' \
        | sort | cut -d: -f2
        rm "$tmp"
        return 0
    fi
    rm "$tmp"
    exit 1
}

update_checksum_file() {
    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
        return 0
    else
        echo 'Getting checksum file from erlang.org...'
        curl -f -L -o "$KERL_DOWNLOAD_DIR"/MD5 "$ERLANG_DOWNLOAD_URL"/MD5 || exit 1
    fi
}

ensure_checksum_file() {
    if [ ! -s "$KERL_DOWNLOAD_DIR"/MD5 ]; then
        update_checksum_file
    fi
}

check_releases() {
    if [ ! -f "$KERL_BASE_DIR"/otp_releases ]; then
        get_releases >"$KERL_BASE_DIR"/otp_releases
    fi
}

is_valid_release() {
    check_releases
    while read -r rel; do
        if [ "$1" = "$rel" ]; then
            return 0
        fi
    done <"$KERL_BASE_DIR"/otp_releases
    return 1
}

assert_valid_release() {
    if ! is_valid_release "$1"; then
        echo "$1 is not a valid Erlang/OTP release"
        exit 1
    fi
    return 0
}

get_release_from_name() {
    if [ -f "$KERL_BASE_DIR"/otp_builds ]; then
        while read -r l; do
            rel=$(echo "$l" | cut -d, -f1)
            name=$(echo "$l" | cut -d, -f2)
            if [ "$name" = "$1" ]; then
                echo "$rel"
                return 0
            fi
        done <"$KERL_BASE_DIR"/otp_builds
    fi
    return 1
}

get_newest_valid_release() {
    check_releases

    rel=$(tail -1 "$KERL_BASE_DIR"/otp_releases)

    if [ -n "$rel" ]; then
        echo "$rel"
        return 0
    fi

    return 1
}

is_valid_installation() {
    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
        while read -r l; do
            name=$(echo "$l" | cut -d' ' -f1)
            path=$(echo "$l" | cut -d' ' -f2)
            if [ "$name" = "$1" ] || [ "$path" = "$1" ]; then
                if [ -f "$path"/activate ]; then
                    return 0
                fi
            fi
        done <"$KERL_BASE_DIR"/otp_installations
    fi
    return 1
}

assert_valid_installation() {
    if ! is_valid_installation "$1"; then
        echo "$1 is not a kerl-managed Erlang/OTP installation"
        exit 1
    fi
    return 0
}

assert_build_name_unused() {
    if [ -f "$KERL_BASE_DIR"/otp_builds ]; then
        while read -r l; do
            name=$(echo "$l" | cut -d, -f2)
            if [ "$name" = "$1" ]; then
                echo "There's already a build named $1"
                exit 1
            fi
        done <"$KERL_BASE_DIR"/otp_builds
    fi
}

_check_required_pkgs() {
    has_dpkg=$(command -v dpkg)
    has_rpm=$(command -v rpm)
    if [ -n "$has_dpkg" ] || [ -n "$has_rpm" ]; then
        # found either dpkg or rpm (or maybe even both!)
        if [ -n "$has_dpkg" ] && [ -n "$has_rpm" ]; then
            echo 'WARNING: You appear to have BOTH rpm and dpkg. This is very strange. No package checks done.'
        elif [ -n "$has_dpkg" ]; then
            _check_dpkg
        elif [ -n "$has_rpm" ]; then
            _check_rpm
        fi
    fi
}

_dpkg_is_installed() {
    # gratefully stolen from
    # https://superuser.com/questions/427318/test-if-a-package-is-installed-in-apt
    # returns 0 (true) if found, 1 otherwise
    dpkg-query -Wf'${db:Status-abbrev}' "$1" 2>/dev/null | \grep -q '^i'
}

_check_dpkg() {
    required='
libssl-dev
make
automake
autoconf
libncurses5-dev
gcc
'
    for pkg in $required; do
        if ! _dpkg_is_installed "$pkg"; then
            echo "WARNING: It appears that a required development package '$pkg' is not installed."
        fi
    done
}

_rpm_is_installed() {
    rpm --quiet -q "$1" >/dev/null 2>&1
}

_check_rpm() {
    required='
openssl-devel
make
automake
autoconf
ncurses-devel
gcc
'
    for pkg in $required; do
        if ! _rpm_is_installed "$pkg"; then
            echo "WARNING: It appears a required development package '$pkg' is not installed."
        fi
    done
}

do_git_build() {
    assert_build_name_unused "$3"

    GIT=$(printf '%s' "$1" | $MD5SUM | cut -d ' ' -f $MD5SUM_FIELD)
    mkdir -p "$KERL_GIT_DIR" || exit 1
    cd "$KERL_GIT_DIR" || exit 1
    echo "Checking out Erlang/OTP git repository from $1..."
    if [ ! -d "$GIT" ]; then
        if ! git clone -q --mirror "$1" "$GIT" >/dev/null 2>&1; then
            echo 'Error mirroring remote git repository'
            exit 1
        fi
    fi
    cd "$GIT" || exit 1
    if ! git remote update --prune >/dev/null 2>&1; then
        echo 'Error updating remote git repository'
        exit 1
    fi

    rm -Rf "${KERL_BUILD_DIR:?}/$3"
    mkdir -p "$KERL_BUILD_DIR/$3" || exit 1
    cd "$KERL_BUILD_DIR/$3" || exit 1
    if ! git clone -l "$KERL_GIT_DIR/$GIT" otp_src_git >/dev/null 2>&1; then
        echo 'Error cloning local git repository'
        exit 1
    fi
    cd otp_src_git || exit 1
    if ! git checkout "$2" >/dev/null 2>&1; then
        if ! git checkout -b "$2" "$2" >/dev/null 2>&1; then
            echo 'Could not checkout specified version'
            rm -Rf "${KERL_BUILD_DIR:?}/$3"
            exit 1
        fi
    fi
    if [ ! -x otp_build ]; then
        echo 'Not a valid Erlang/OTP repository'
        rm -Rf "${KERL_BUILD_DIR:?}/$3"
        exit 1
    fi
    echo "Building Erlang/OTP $3 from git, please wait..."
    if [ -z "$KERL_BUILD_AUTOCONF" ]; then
        KERL_USE_AUTOCONF=1
    fi
    _do_build 'git' "$3"
    echo "Erlang/OTP $3 from git has been successfully built"
    list_add builds git,"$3"
}

get_otp_version() {
    echo "$1" | sed $SED_OPT -e 's/R?([0-9]{1,2}).+/\1/'
}

get_perl_version() {
    if assert_perl; then
        # This is really evil but it's portable and it works. Don't @ me bro
        perl -e 'print int(($] - 5)*1000)'
    else
        echo 'FATAL: could not find perl which is required to compile Erlang.'
        exit 1
    fi
}

assert_perl() {
    perl_loc=$(command -v perl)
    if [ -z "$perl_loc" ]; then
        return 1
    else
        # 0 to bash is "true" because of Unix exit code conventions
        return 0
    fi
}

get_javac_version() {
    java_loc=$(command -v javac)
    if [ -z "$java_loc" ]; then
        # Java's not installed, so just return 0
        0
    else
        javaout=$(javac -version 2>&1)
        echo "$javaout" | cut -d' ' -f2 | cut -d. -f2
    fi
}

show_configuration_warnings() {
    # $1 is logfile
    # $2 is section header (E.g. "APPLICATIONS DISABLED")
    # Find the row number for the section we are looking for
    INDEX=$(\grep -n -m1 "$2" "$1" | cut -d: -f1)

    # If there are no warnings, the section won't appear in the log
    if [ -n "$INDEX" ]; then
        # Skip the section header, find the end line and skip it
        # then print the results indented
        tail -n +$((INDEX+3)) "$1" | \
            sed -n '1,/\*/p' | \
            awk -F: -v logfile="$1" -v section="$2" \
                'BEGIN { printf "%s (See: %s)\n", section, logfile }
                 /^[^\*]/ { print " *", $0 }
                 END { print "" } '
    fi
}

show_logfile() {
    echo "$1"
    tail "$2"
    echo
    echo "Please see $2 for full details."
}

maybe_patch() {
    # $1 = OS platform e.g., Darwin, etc
    # $2 = OTP release

    release=$(get_otp_version "$2")
    case "$1" in
        Darwin)
            maybe_patch_darwin "$release"
            # Catalina and clang require a "no-weaks-import" flag during build
            maybe_patch_catalina "$release" "$2"
            maybe_patch_bigsur "$release"
            ;;
        SunOS)
            maybe_patch_sunos "$release"
            ;;
        *)
            ;;
    esac

    maybe_patch_all "$release"
}

maybe_patch_all() {
    perlver=$(get_perl_version)
    if [ "$perlver" -ge 22 ]; then
        case "$1" in
            14)
                apply_r14_beam_makeops_patch >>"$LOGFILE"
                ;;
            15)
                apply_r15_beam_makeops_patch >>"$LOGFILE"
                ;;
            *)
                ;;
        esac
    fi

    # Are we building docs?
    if [ -n "$KERL_BUILD_DOCS" ]; then
        if [ "$1" -le 16 ]; then
            javaver=$(get_javac_version)
            if [ "$javaver" -ge 8 ]; then
                apply_javadoc_linting_patch >>"$LOGFILE"
            fi
        fi
    fi

    # Maybe apply zlib patch
    if [ "$1" -ge 17 ] && [ "$1" -le 19 ]; then
        apply_zlib_patch >> "$LOGFILE"
    fi
}

maybe_patch_darwin() {
    # Reminder: $1 = OTP release version
    if [ "$1" -le 14 ]; then
        CFLAGS='-DERTS_DO_INCL_GLB_INLINE_FUNC_DEF'
        apply_darwin_compiler_patch >>"$LOGFILE"
    elif [ "$1" -eq 16 ]; then
        apply_r16_wx_ptr_patch >>"$LOGFILE"
    elif [ "$1" -ge 17 ] && [ "$1" -le 19 ]; then
        apply_wx_ptr_patch >>"$LOGFILE"
    elif [ "$1" -ge 21 ] && [ "$1" -le 23 ]; then
        apply_in6addr_test_patch >> "$LOGFILE"
    fi
}

maybe_patch_catalina() {
    clang_version=$(/usr/bin/clang --version  | head -1  | awk '{print $4}')
    command_line_tools_version=$(xcode-select -v | awk '{print $3}' | sed 's/.$//')
    release="$1"
    otp_version="$2"

    if is_osx_catalina && \
        [[ "$release" -lt 23 ]] && \
        compare_sem_version $otp_version "<" "22.3.1" && \
        compare_sem_version $clang_version ">" "11.4" && \
        [[ "$command_line_tools_version" -le 2373 ]]; then
        apply_catalina_no_weak_imports_patch >>"$LOGFILE"
    fi
}

is_osx_catalina() {
    [[ $(uname -r) == "19"* ]]
}

compare_sem_version() {
    version=$1
    operator=$2
    baseline=$3

    case $operator in
        '<')
            [[ $(printf "$version\n$baseline" | sort --version-sort | head -n 1) != "$baseline" ]]
            ;;
        ('>' | "==")
            [[ $(printf "$version\n$baseline" | sort --version-sort | head -n 1) == "$baseline" ]]
            ;;
    esac
}

apply_catalina_no_weak_imports_patch() {
    patch -p1 <<'_END_PATCH'
diff --git a/erts/configure.in b/erts/configure.in
index 3ba8216a19..d7cebc5ebc 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -926,20 +926,16 @@ dnl for now that is the way we do it.
 USER_LD=$LD
 USER_LDFLAGS="$LDFLAGS"
 LD='$(CC)'
+
 case $host_os in
-     darwin*)
-	saved_LDFLAGS="$LDFLAGS"
-	LDFLAGS="$LDFLAGS -Wl,-no_weak_imports"
-	AC_TRY_LINK([],[],
-		[
-			LD_MAY_BE_WEAK=no
-		],
-		[
-			LD_MAY_BE_WEAK=yes
-			LDFLAGS="$saved_LDFLAGS"
-		]);;
-    *)
-	LD_MAY_BE_WEAK=no;;
+        darwin19*)
+	    # Disable stack checking to avoid crashing with a segment fault
+	    # in macOS Catalina.
+	    AC_MSG_NOTICE([Turning off stack check on macOS 10.15 (Catalina)])
+	    CFLAGS="-Wno-error=implicit-function-declaration -fno-stack-check $CFLAGS"
+	    ;;
+        *)
+	    ;;
 esac

 AC_SUBST(LD)
_END_PATCH
}

# https://github.com/erlang/otp/commit/f1044ef9e35da26f276b8127640e177d67aade6a.diff
maybe_patch_bigsur() {
    release="$1"
    if is_osx_bigsur && \
        [[ "$release" -lt 24 ]]; then
        apply_bigsur_bypass_version_check >>"$LOGFILE"
    fi
}

is_osx_bigsur() {
    [[ $(uname -r) == "20"* ]]
}

apply_bigsur_bypass_version_check() {
    if [ -f make/configure.in ]; then
        echo "Bypassing version check in make/configure.in for Big Sur"
        sed -i '' 's/\(#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version\)/\1 \&\& false/' make/configure.in
    elif [ -f configure.in ]; then
        echo "Bypassing version check in configure.in for Big Sur"
        sed -i '' 's/\(#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version\)/\1 \&\& false/' configure.in
        echo "Removng no_weak_imports from LDFLAGS in erts/configure.in for Big Sur"
        sed -i '' 's/LDFLAGS="$LDFLAGS -Wl,-no_weak_imports"//' erts/configure.in
    fi
}

maybe_patch_sunos() {
    if [ "$1" -le 14 ]; then
        apply_solaris_networking_patch >>"$LOGFILE"
    fi
}

do_normal_build() {
    assert_valid_release "$1"
    assert_build_name_unused "$2"
    FILENAME=""
    download "$1"
    mkdir -p "$KERL_BUILD_DIR/$2" || exit 1
    if [ ! -d "$KERL_BUILD_DIR/$2/$FILENAME" ]; then
        echo 'Extracting source code'
        UNTARDIRNAME="$KERL_BUILD_DIR/$2/$FILENAME-kerluntar-$$"
        rm -rf "$UNTARDIRNAME"
        mkdir -p "$UNTARDIRNAME" || exit 1
        # github tarballs have a directory in the form of "otp[_-]TAGNAME"
        # Ericsson tarballs have the classic otp_src_RELEASE pattern
        # Standardize on Ericsson format because that's what the rest of the script expects
        (cd "$UNTARDIRNAME" && tar xzf "$KERL_DOWNLOAD_DIR/$FILENAME".tar.gz && cp -rfp ./* "$KERL_BUILD_DIR/$2/otp_src_$1")
        rm -rf "$UNTARDIRNAME"
    fi

    echo "Building Erlang/OTP $1 ($2), please wait..."
    _do_build "$1" "$2"
    echo "Erlang/OTP $1 ($2) has been successfully built"
    list_add builds "$1,$2"
}

_flags() {
    # We used to munge the LD and DED flags for clang 9/10 shipped with
    # High Sierra (macOS 10.13), Mojave (macOS 10.14) and Catalina
    # (macOS 10.15)
    #
    # As of OTP 20.1 that is (apparently) no longer necessary and
    # in OTP 24 breaks stuff. See thread and comment here:
    # https://github.com/erlang/otp/issues/4821#issuecomment-845914942
    case "$KERL_SYSTEM" in
        Darwin)
            # Make sure we don't overwrite stuff that someone who
            # knows better than us set.
            if [ -z "$CC" ]; then
                CC='clang'
            fi

            CFLAGS="$CFLAGS" CC="$CC" "$@"
            ;;
        *)
            CFLAGS="$CFLAGS" "$@"
            ;;
    esac
}

_do_build() {
    case "$KERL_SYSTEM" in
        Darwin)
            # Ensure that the --enable-darwin-64bit flag is set on all macOS
            # That way even on older Erlangs we get 64 bit Erlang builds
            # macOS has been mandatory 64 bit for a while
            if ! echo "$KERL_CONFIGURE_OPTIONS" | \grep 'darwin-64bit' >/dev/null 2>&1; then
                KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS "--enable-darwin-64bit
            fi

            # Attempt to use brew to discover if and where openssl has been
            # installed unless the user has already explictly set it.

            if ! echo "$KERL_CONFIGURE_OPTIONS" | \grep 'with-ssl' >/dev/null 2>&1; then
                whichbrew=$(command -v brew)
                if [ -n "$whichbrew" ] && [ -x "$whichbrew" ]; then
                    brew_prefix=$(brew --prefix openssl@1.1)
                    if [ -n "$brew_prefix" ] && [ -d "$brew_prefix" ]; then
                        KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS "--with-ssl=$brew_prefix
                    fi
                fi
            fi
        ;;
        Linux)
            # we are going to check here to see if the Linux uses dpkg or rpms
            #
            # this is a "best effort" attempt to discover if a Linux has the
            # packages needed to build Erlang. We will always assume the user
            # knows better than us and are going to go ahead and try to build
            # Erlang anyway. But at least it will be a clear warning to the
            # user if a build fails.
            _check_required_pkgs
            ;;
        *)
        ;;
    esac

    if [ -n "$KERL_BUILD_DOCS" ]; then
        KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --prefix=$KERL_BUILD_DIR/$2/release_$1"
    fi

    ERL_TOP="$KERL_BUILD_DIR/$2/otp_src_$1"
    cd "$ERL_TOP" || exit 1
    LOGFILE="$KERL_BUILD_DIR/$2/otp_build_$1.log"

    # Set configuation flags given applications white/black lists
    if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
        for app in $KERL_CONFIGURE_APPLICATIONS; do
            case "$KERL_CONFIGURE_OPTIONS" in
                *"--with-$app"*)
                    echo "Option '--with-$app' in KERL_CONFIGURE_OPTIONS is superfluous" ;;
                *)
                    KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --with-$app" ;;
            esac
        done
    fi
    if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
        for app in $KERL_CONFIGURE_DISABLE_APPLICATIONS; do
            case "$KERL_CONFIGURE_OPTIONS" in
                *"--without-$app"*)
                    echo "Option '--without-$app' in KERL_CONFIGURE_OPTIONS is superfluous" ;;
                *)
                    KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --without-$app" ;;
            esac
        done
    fi

    # Check to see if configuration options need to be stored or have changed
    TMPOPT="${TMP_DIR}/kerloptions.$$"
    echo "$CFLAGS" >"$TMPOPT"
    echo "$KERL_CONFIGURE_OPTIONS" >>"$TMPOPT"
    SUM=$($MD5SUM "$TMPOPT" | cut -d ' ' -f $MD5SUM_FIELD)
    # Check for a .kerl_config.md5 file
    if [ -e ./"$KERL_CONFIG_STORAGE_FILENAME".md5 ]; then
        # Compare our current options to the saved ones
        read -r OLD_SUM <./"$KERL_CONFIG_STORAGE_FILENAME".md5
        if [ "$SUM" != "$OLD_SUM" ]; then
            echo 'Configure options have changed. Reconfiguring...'
            rm -f configure
            mv "$TMPOPT" ./"$KERL_CONFIG_STORAGE_FILENAME"
            echo "$SUM" >./"$KERL_CONFIG_STORAGE_FILENAME".md5
        else
            # configure options are the same
            rm -f "$TMPOPT"
        fi
    else
	# no file exists, so write one
	mv "$TMPOPT" .kerl_config
	echo "$SUM" >.kerl_config.md5
    fi

    # Don't apply patches to "custom" git builds. We have no idea if they will apply
    # cleanly or not.
    if [ "$1" != 'git' ]; then
        maybe_patch "$KERL_SYSTEM" "$1"
    fi
    if [ -n "$KERL_USE_AUTOCONF" ]; then
        # shellcheck disable=SC2086
        ./otp_build autoconf $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1 && \
           _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1
    else
        # shellcheck disable=SC2086
        _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1

    fi
    if echo "$KERL_CONFIGURE_OPTIONS" | \grep -- '--enable-native-libs' >/dev/null 2>&1; then
        make clean >>"$LOGFILE" 2>&1
        # shellcheck disable=SC2086
        if ! _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1; then
            show_logfile 'Configure failed.' "$LOGFILE"
            list_remove builds "$1 $2"
            exit 1
        fi
    fi

    for SECTION in 'APPLICATIONS DISABLED' \
                   'APPLICATIONS INFORMATION' \
                   'DOCUMENTATION INFORMATION'; do
        show_configuration_warnings "$LOGFILE" "$SECTION"
    done

    if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
        \find ./lib -maxdepth 1 -type d -exec touch -f {}/SKIP \;
        for app in $KERL_CONFIGURE_APPLICATIONS; do
            if ! rm ./lib/"$app"/SKIP; then
                echo "Couldn't prepare '$app' application for building"
                list_remove builds "$1 $2"
                exit 1
            fi
        done
    fi
    if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
        for app in $KERL_CONFIGURE_DISABLE_APPLICATIONS; do
            if ! touch -f ./lib/"$app"/SKIP; then
                echo "Couldn't disable '$app' application for building"
                exit 1
            fi
        done
    fi

    # shellcheck disable=SC2086
    if ! _flags ./otp_build boot -a $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1; then
        show_logfile 'Build failed.' "$LOGFILE"
        list_remove builds "$1 $2"
        exit 1
    fi
    if [ -n "$KERL_BUILD_DOCS" ]; then
        echo 'Building docs...'
        release=$(get_otp_version "$2")
        if ! make docs "DOC_TARGETS=$KERL_DOC_TARGETS" >>"$LOGFILE" 2>&1; then
            show_logfile 'Building docs failed.' "$LOGFILE"
            list_remove builds "$1 $2"
            exit 1
        fi
        if ! make release_docs "DOC_TARGETS=$KERL_DOC_TARGETS" "RELEASE_ROOT=$KERL_BUILD_DIR/$2/release_$1" >>"$LOGFILE" 2>&1; then
            show_logfile 'Release of docs failed.' "$LOGFILE"
            list_remove builds "$1 $2"
            exit 1
        fi
    fi
    rm -f "$LOGFILE"
    ERL_TOP="$ERL_TOP" ./otp_build release -a "$KERL_BUILD_DIR/$2/release_$1" >/dev/null 2>&1
    cd "$KERL_BUILD_DIR/$2/release_$1" || exit 1
    ./Install $INSTALL_OPT "$KERL_BUILD_DIR/$2/release_$1" >/dev/null 2>&1
    if [ -n "$KERL_BUILD_DEBUG_VM" ]; then
        echo "Also building Erlang/OTP $1 ($2) debug VM, please wait..."
        cd $ERL_TOP/erts/emulator || exit 1
        make debug ERL_TOP=$ERL_TOP >>"$LOGFILE" 2>&1
    fi
}

do_install() {
    if ! rel=$(get_release_from_name "$1"); then
        echo "No build named $1"
        exit 1
    fi
    if ! is_valid_install_path "$2"; then
        exit 1
    fi
    mkdir -p "$2" || exit 1
    absdir=$(cd "$2" && pwd)
    echo "Installing Erlang/OTP $rel ($1) in $absdir..."
    ERL_TOP="$KERL_BUILD_DIR/$1/otp_src_$rel"
    cd "$ERL_TOP" || exit 1
    if ! (ERL_TOP="$ERL_TOP" ./otp_build release -a "$absdir" >/dev/null 2>&1 &&
              cd "$absdir" && ./Install $INSTALL_OPT "$absdir" >/dev/null 2>&1); then
        echo "Couldn't install Erlang/OTP $rel ($1) in $absdir"
        exit 1
    fi
    BEAM_DEBUG_SMP=$(\find . -name beam.debug.smp)
    if [ "$BEAM_DEBUG_SMP" != "" ]; then
        ERL_CHILD_SETUP_DEBUG=$(\find . -name erl_child_setup.debug)
        echo "Also installing Erlang/OTP $rel ($1) debug VM in $absdir..."
        cp $BEAM_DEBUG_SMP $absdir/erts-*/bin/
        cp $ERL_CHILD_SETUP_DEBUG $absdir/erts-*/bin/
        cp bin/cerl $absdir/bin
    fi
    list_add installations "$1 $absdir";
    cat <<ACTIVATE >"$absdir"/activate
#!/bin/sh
# credits to virtualenv
kerl_deactivate() {
    if [ -n "\$_KERL_SAVED_ERL_AFLAGS" ]; then
        ERL_AFLAGS="\$_KERL_SAVED_ERL_AFLAGS"
        export ERL_AFLAGS
        unset _KERL_SAVED_ERL_AFLAGS
    fi
    if [ -n "\$_KERL_PATH_REMOVABLE" ]; then
        # shellcheck disable=SC2001
        PATH="\$(echo "\$PATH" | sed -e "s#\$_KERL_PATH_REMOVABLE:##")"
        export PATH
        unset _KERL_PATH_REMOVABLE
    fi
    if [ -n "\$_KERL_MANPATH_REMOVABLE" ]; then
        # shellcheck disable=SC2001
        MANPATH="\$(echo "\$MANPATH" | sed -e "s#\$_KERL_MANPATH_REMOVABLE:##")"
        export MANPATH
        unset _KERL_MANPATH_REMOVABLE
    fi
    if [ -n "\$_KERL_SAVED_REBAR_PLT_DIR" ]; then
        REBAR_PLT_DIR="\$_KERL_SAVED_REBAR_PLT_DIR"
        export REBAR_PLT_DIR
        unset _KERL_SAVED_REBAR_PLT_DIR
    fi
    if [ -n "\$_KERL_ACTIVE_DIR" ]; then
        unset _KERL_ACTIVE_DIR
    fi
    if [ -n "\$_KERL_SAVED_PS1" ]; then
        PS1="\$_KERL_SAVED_PS1"
        export PS1
        unset _KERL_SAVED_PS1
    fi
    if [ -n "\$_KERL_DOCSH_DOT_ERLANG" ]; then
        rm "\$HOME/.erlang"
        unset _KERL_DOCSH_DOT_ERLANG
    fi
    if [ -n "\$_KERL_DOCSH_USER_DEFAULT" ]; then
        unset DOCSH_USER_DEFAULT
        unset _KERL_DOCSH_USER_DEFAULT
    fi
    if [ -n "\$_KERL_ERL_CALL_REMOVABLE" ]; then
        # shellcheck disable=SC2001
        PATH="\$(echo "\$PATH" | sed -e "s#\$_KERL_ERL_CALL_REMOVABLE:##")"
        export PATH
        unset _KERL_ERL_CALL_REMOVABLE
    fi
    if [ -n "\$BASH" ] || [ -n "\$ZSH_VERSION" ]; then
        hash -r
    fi
    if [ ! "\$1" = "nondestructive" ]; then
        unset -f kerl_deactivate
    fi
    unset KERL_ENABLE_PROMPT
    unset KERL_PROMPT_FORMAT
}
kerl_deactivate nondestructive

_KERL_SAVED_REBAR_PLT_DIR="\$REBAR_PLT_DIR"
export _KERL_SAVED_REBAR_PLT_DIR
_KERL_PATH_REMOVABLE="$absdir/bin"
PATH="\${_KERL_PATH_REMOVABLE}:\$PATH"
export PATH _KERL_PATH_REMOVABLE
_KERL_MANPATH_REMOVABLE="$absdir/lib/erlang/man:$absdir/man"
MANPATH="\${_KERL_MANPATH_REMOVABLE}:\$MANPATH"
export MANPATH _KERL_MANPATH_REMOVABLE
REBAR_PLT_DIR="$absdir"
export REBAR_PLT_DIR
_KERL_ACTIVE_DIR="$absdir"
export _KERL_ACTIVE_DIR
_KERL_ERL_CALL_REMOVABLE=\$(\\find $absdir -type d -wholename "*erl_interface*/bin")
PATH="\${_KERL_ERL_CALL_REMOVABLE}:\$PATH"
export PATH _KERL_ERL_CALL_REMOVABLE
# https://twitter.com/mononcqc/status/877544929496629248
export _KERL_SAVED_ERL_AFLAGS=" \$ERL_AFLAGS"
kernel_history=\$(echo "\$ERL_AFLAGS" | \\grep 'kernel shell_history' || true)
if [ -z "\$kernel_history" ]; then
    export ERL_AFLAGS="-kernel shell_history enabled \$ERL_AFLAGS"
fi
# shellcheck source=/dev/null
if [ -f "$KERL_CONFIG" ]; then . "$KERL_CONFIG"; fi
if [ -n "\$KERL_ENABLE_PROMPT" ]; then
    _KERL_SAVED_PS1="\$PS1"
    export _KERL_SAVED_PS1
    if [ -n "\$KERL_PROMPT_FORMAT" ]; then
        FRMT="\$KERL_PROMPT_FORMAT"
    else
        FRMT="(%BUILDNAME%)"
    fi
    PRMPT=\$(echo "\$FRMT" | sed 's^%RELEASE%^$rel^;s^%BUILDNAME%^$1^')
    PS1="\$PRMPT\$PS1"
    export PS1
fi
if [ -d "$absdir/lib/docsh" ]; then
    export DOCSH_USER_DEFAULT="$absdir/lib/docsh/user_default"
    export _KERL_DOCSH_USER_DEFAULT=yes
    if [ -f "\$HOME/.erlang" ]; then
        # shellcheck disable=SC2153
        if [ ! x"\$KERL_DOCSH_DOT_ERLANG" = x'exists' ]; then
            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
            echo "Please make sure \$HOME/.erlang contains code"
            echo "from $absdir/lib/docsh/dot.erlang"
            echo 'and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning.'
        fi
    else
        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
        export _KERL_DOCSH_DOT_ERLANG=yes
    fi
fi
if [ -n "\$BASH" ] || [ -n "\$ZSH_VERSION" ]; then
    hash -r
fi
ACTIVATE

    cat <<ACTIVATE_FISH >"$absdir"/activate.fish
# credits to virtualenv
function _kerl_remove_el --description 'remove element from array'
    set -l new_array
    for el in \$\$argv[1]
        if test \$el != \$argv[2]
            set new_array \$new_array \$el
        end
    end
    set -x \$argv[1] \$new_array
end

function kerl_deactivate --description "deactivate erlang environment"
    if set --query _KERL_PATH_REMOVABLE
        _kerl_remove_el PATH "\$_KERL_PATH_REMOVABLE"
        set --erase _KERL_PATH_REMOVABLE
    end
    if set --query _KERL_MANPATH_REMOVABLE
        _kerl_remove_el MANPATH "\$_KERL_MANPATH_REMOVABLE"
        set --erase _KERL_MANPATH_REMOVABLE
    end
    if set --query _KERL_SAVED_REBAR_PLT_DIR
        set -x REBAR_PLT_DIR "\$_KERL_SAVED_REBAR_PLT_DIR"
        set --erase _KERL_SAVED_REBAR_PLT_DIR
    end
    if set --query _KERL_ACTIVE_DIR
        set --erase _KERL_ACTIVE_DIR
    end
    if functions --query _kerl_saved_prompt
        functions --erase fish_prompt
        # functions --copy complains about about fish_prompt already being defined
        # so we take a page from virtualenv's book
        . ( begin
                printf "function fish_prompt\\n\\t#"
                functions _kerl_saved_prompt
            end | psub )
        functions --erase _kerl_saved_prompt
    end
    if set --query _KERL_DOCSH_DOT_ERLANG
        rm "\$HOME/.erlang"
        set --erase _KERL_DOCSH_DOT_ERLANG
    end
    if set --query _KERL_DOCSH_USER_DEFAULT
        set --erase DOCSH_USER_DEFAULT
        set --erase _KERL_DOCSH_USER_DEFAULT
    end
    if set --query _KERL_ERL_CALL_REMOVABLE
        _kerl_remove_el PATH "\$_KERL_ERL_CALL_REMOVABLE"
        set --erase _KERL_ERL_CALL_REMOVABLE
    end
    if test "\$argv[1]" != "nondestructive"
        functions --erase kerl_deactivate
        functions --erase _kerl_remove_el
    end
end
kerl_deactivate nondestructive

set -x _KERL_SAVED_REBAR_PLT_DIR "\$REBAR_PLT_DIR"
set -x _KERL_PATH_REMOVABLE "$absdir/bin"
set -x PATH "\$_KERL_PATH_REMOVABLE" \$PATH
set -x _KERL_MANPATH_REMOVABLE "$absdir/lib/erlang/man" "$absdir/man"
set -x MANPATH \$MANPATH "\$_KERL_MANPATH_REMOVABLE"
set -x REBAR_PLT_DIR "$absdir"
set -x _KERL_ACTIVE_DIR "$absdir"
set -x _KERL_ERL_CALL_REMOVABLE (\\find "$absdir" -type d -path "*erl_interface*/bin")
set -x PATH "\$_KERL_ERL_CALL_REMOVABLE" \$PATH

if test -f "$KERL_CONFIG.fish"
    source "$KERL_CONFIG.fish"
end
if set --query KERL_ENABLE_PROMPT
    functions --copy fish_prompt _kerl_saved_prompt
    function fish_prompt
        echo -n "($1)"
        _kerl_saved_prompt
    end
end
if test -d "$absdir/lib/docsh"
    set -x DOCSH_USER_DEFAULT "$absdir/lib/docsh/user_default"
    set -x _KERL_DOCSH_USER_DEFAULT yes
    if test -f "\$HOME/.erlang"
        if test ! x"\$KERL_DOCSH_DOT_ERLANG" = x"exists"
            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
            echo "Please make sure \$HOME/.erlang contains code"
            echo "from $absdir/lib/docsh/dot.erlang"
            echo "and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning."
        end
    else
        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
        set -x _KERL_DOCSH_DOT_ERLANG yes
    end
end
ACTIVATE_FISH

    cat <<ACTIVATE_CSH >"$absdir"/activate.csh
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.

alias kerl_deactivate 'test \$?_KERL_SAVED_PATH != 0 && setenv PATH "\$_KERL_SAVED_PATH" && unset _KERL_SAVED_PATH; rehash; test \$?_KERL_SAVED_MANPATH != 0 && setenv MANPATH "\$_KERL_SAVED_MANPATH" && unset _KERL_SAVED_MANPATH; test \$?_KERL_SAVED_REBAR_PLT_DIR != 0 && setenv REBAR_PLT_DIR "\$_KERL_SAVED_REBAR_PLT_DIR" && unset _KERL_SAVED_REBAR_PLT_DIR; test \$?_KERL_ACTIVE_DIR != 0 && unset _KERL_ACTIVE_DIR; test \$?_KERL_DOCSH_USER_DEFAULT != 0 && unsetenv DOCSH_USER_DEFAULT && unset _KERL_DOCSH_USER_DEFAULT; test \$?_KERL_ERL_CALL_REMOVABLE != 0 && unset _KERL_ERL_CALL_REMOVABLE; test \$?_KERL_DOCSH_DOT_ERLANG != 0 && rm "\$HOME/.erlang" && unset _KERL_DOCSH_DOT_ERLANG; test \$?_KERL_SAVED_PROMPT != 0 && set prompt="\$_KERL_SAVED_PROMPT" && unset _KERL_SAVED_PROMPT; test "!:*" != "nondestructive" && unalias deactivate'

# Unset irrelevant variables.
kerl_deactivate nondestructive

if ( \$?REBAR_PLT_DIR ) then
    set _KERL_SAVED_REBAR_PLT_DIR = "\$REBAR_PLT_DIR"
else
    set _KERL_SAVED_REBAR_PLT_DIR=""
endif

set _KERL_PATH_REMOVABLE = "$absdir/bin"
set _KERL_SAVED_PATH = "\$PATH"
setenv PATH "\${_KERL_PATH_REMOVABLE}:\$PATH"

if ( ! \$?MANPATH ) then
    set MANPATH = ""
endif
set _KERL_MANPATH_REMOVABLE = "$absdir/lib/erlang/man:$absdir/man"
set _KERL_SAVED_MANPATH = "\$MANPATH"
setenv MANPATH "\${_KERL_MANPATH_REMOVABLE}:\$MANPATH"

setenv REBAR_PLT_DIR "$absdir"

set _KERL_ACTIVE_DIR = "$absdir"

set _KERL_ERL_CALL_REMOVABLE = $(\find "$absdir" -type d -wholename "*erl_interface*/bin")
setenv PATH "\${_KERL_ERL_CALL_REMOVABLE}:\$PATH"

if ( -f "$KERL_CONFIG.csh" ) then
    source "$KERL_CONFIG.csh"
endif

if ( \$?KERL_ENABLE_PROMPT ) then
    set _KERL_SAVED_PROMPT = "\$prompt"

    if ( \$?KERL_PROMPT_FORMAT ) then
        set FRMT = "\$KERL_PROMPT_FORMAT"
    else
        set FRMT = "(%BUILDNAME%)"
    endif

    set PROMPT = \$(echo "\$FRMT" | sed 's^%RELEASE%^$rel^;s^%BUILDNAME%^$1^')
    set prompt = "\$PROMPT\$prompt"
endif

if ( -d "$absdir/lib/docsh" ) then
    setenv DOCSH_USER_DEFAULT "$absdir/lib/docsh/user_default"
    set _KERL_DOCSH_USER_DEFAULT = "yes"
    if ( -f "\$HOME/.erlang" ) then
        if ( \$?KERL_DOCSH_DOT_ERLANG == 0 ) then
            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
            echo "Please make sure \$HOME/.erlang contains code"
            echo "from $absdir/lib/docsh/dot.erlang"
            echo "and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning."
        endif
    else
        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
        set _KERL_DOCSH_DOT_ERLANG = "yes"
    endif
endif

rehash
ACTIVATE_CSH

    if [ -n "$KERL_BUILD_DOCS" ]; then
        if ! (ERL_TOP="$ERL_TOP" make release_docs "DOC_TARGETS=$KERL_DOC_TARGETS" "RELEASE_ROOT=$absdir" >/dev/null 2>&1); then
            echo "Couldn't install docs for Erlang/OTP $rel ($1) in $absdir"
            exit 1
        fi
    else
        if [ "$KERL_BUILD_BACKEND" = 'tarball' ]; then
            if [ "$rel" != 'git' ]; then
                if [ -n "$KERL_INSTALL_MANPAGES" ]; then
                    echo 'Fetching and installing manpages...'
                    download_manpages "$rel"
                fi

                if [ -n "$KERL_INSTALL_HTMLDOCS" ]; then
                    echo 'Fetching and installing HTML docs...'
                    download_htmldocs "$rel"
                fi
            fi
        fi
    fi

    KERL_CONFIG_STORAGE_PATH="$KERL_BUILD_DIR/$1/otp_src_$rel/$KERL_CONFIG_STORAGE_FILENAME"
    [ -e "$KERL_CONFIG_STORAGE_PATH" ] && cp "$KERL_CONFIG_STORAGE_PATH" "$absdir/$KERL_CONFIG_STORAGE_FILENAME"

    if [ -n "$KERL_BUILD_PLT" ]; then
        echo 'Building Dialyzer PLT...'
        build_plt "$absdir"
    fi

    if command -v apk >/dev/null 2>&1; then
        # Running on Alpine Linux, assuming non-exotic shell
        SHELL_SUFFIX=''
    else
        PID=$$
        PARENT_PID=$(\ps -p $PID -o ppid=) || exit 1
        # shellcheck disable=SC2086
        PARENT_CMD=$(\ps -p $PARENT_PID -o ucomm | tail -n 1)
        case "$PARENT_CMD" in
            fish)
                SHELL_SUFFIX='.fish'
                ;;
            csh)
                SHELL_SUFFIX='.csh'
                ;;
            *)
                SHELL_SUFFIX=''
                ;;
        esac
    fi

    echo 'You can activate this installation running the following command:'
    echo ". $absdir/activate$SHELL_SUFFIX"
    echo 'Later on, you can leave the installation typing:'
    echo 'kerl_deactivate'
}

install_docsh() {
    REPO_URL=$DOCSH_GITHUB_URL
    GIT=$(printf '%s' $REPO_URL | $MD5SUM | cut -d' ' -f $MD5SUM_FIELD)
    BUILDNAME="$1"
    DOCSH_DIR="$KERL_BUILD_DIR/$BUILDNAME/docsh"
    DOCSH_REF='0.7.1'
    ACTIVE_PATH="$2"

    OTP_VERSION=$(get_otp_version "$1")
    # This has to be updated with docsh updates
    DOCSH_SUPPORTED='^1[9]\|2[01]$'
    if ! echo "$OTP_VERSION" | \grep "$DOCSH_SUPPORTED" >/dev/null 2>&1; then
        echo "Erlang/OTP version $OTP_VERSION not supported by docsh (does not match regex $DOCSH_SUPPORTED)"
        exit 1
    fi

    mkdir -p "$KERL_GIT_DIR" || exit 1
    cd "$KERL_GIT_DIR" || exit 1
    echo "Checking out docsh git repository from $REPO_URL..."
    if [ ! -d "$GIT" ]; then
        if ! git clone -q --mirror "$REPO_URL" "$GIT" >/dev/null 2>&1; then
            echo 'Error mirroring remote git repository'
            exit 1
        fi
    fi
    cd "$GIT" || exit 1
    if ! git remote update --prune >/dev/null 2>&1; then
        echo 'Error updating remote git repository'
        exit 1
    fi

    rm -Rf "$DOCSH_DIR"
    mkdir -p "$DOCSH_DIR" || exit 1
    cd "$DOCSH_DIR" || exit 1
    if ! git clone -l "$KERL_GIT_DIR/$GIT" "$DOCSH_DIR" >/dev/null 2>&1; then
        echo 'Error cloning local git repository'
        exit 1
    fi
    cd "$DOCSH_DIR" || exit 1
    if ! git checkout "$DOCSH_REF" >/dev/null 2>&1; then
        if ! git checkout -b "$DOCSH_REF" "$DOCSH_REF" >/dev/null 2>&1; then
            echo 'Could not checkout specified version'
            rm -Rf "$DOCSH_DIR"
            exit 1
        fi
    fi

    if ! ./rebar3 compile; then
        echo 'Could not compile docsh'
        rm -Rf "$DOCSH_DIR"
        exit 1
    fi

    ## Install docsh
    if [ -f "$ACTIVE_PATH"/lib/docsh ]; then
        echo "Couldn't install $ACTIVE_PATH/lib/docsh - the directory already exists"
        rm -Rf "$DOCSH_DIR"
        exit 1
    else
        cp -R "$DOCSH_DIR"/_build/default/lib/docsh "$ACTIVE_PATH"/lib/
    fi
    ## Prepare dot.erlang for linking as $HOME/.erlang
    if [ -f "$ACTIVE_PATH"/lib/docsh/dot.erlang ]; then
        echo "Couldn't install $ACTIVE_PATH/lib/docsh/dot.erlang - the file already exists"
        rm -Rf "$DOCSH_DIR"
        exit 1
    else
        cat "$DOCSH_DIR"/templates/dot.erlang >"$ACTIVE_PATH"/lib/docsh/dot.erlang
    fi
    ## Warn if $HOME/.erlang exists
    if [ -f "$HOME"/.erlang ]; then
        echo "$HOME/.erlang exists - kerl won't be able to symlink a docsh-compatible version."
        echo "Please make sure your $HOME/.erlang contains code"
        echo "from $ACTIVE_PATH/lib/docsh/dot.erlang"
        echo 'and export KERL_DOCSH_DOT_ERLANG=exists to suppress further warnings'
    fi
    ## Install docsh user_default
    if [ -f "$ACTIVE_PATH"/lib/docsh/user_default.beam ]; then
        echo "Couldn't install $ACTIVE_PATH/lib/docsh/user_default.beam - the file already exists"
        rm -Rf "$DOCSH_DIR"
        exit 1
    else
        erlc -I "$DOCSH_DIR"/include -o "$ACTIVE_PATH"/lib/docsh/ "$DOCSH_DIR"/templates/user_default.erl
    fi
}

download_manpages() {
    FILENAME=otp_doc_man_$1.tar.gz
    tarball_download "$FILENAME"
    echo 'Extracting manpages'
    cd "$absdir" && tar xzf "$KERL_DOWNLOAD_DIR/$FILENAME"
}

download_htmldocs() {
    FILENAME=otp_doc_html_"$1".tar.gz
    tarball_download "$FILENAME"
    echo 'Extracting HTML docs'
    (cd "$absdir" && mkdir -p html && tar -C "$absdir"/html -xzf "$KERL_DOWNLOAD_DIR/$FILENAME")
}

build_plt() {
    dialyzerd="$1"/dialyzer
    mkdir -p "$dialyzerd" || exit 1
    plt="$dialyzerd"/plt
    build_log="$dialyzerd"/build.log
    dirs=$(\find "$1"/lib -maxdepth 2 -name ebin -type d -exec dirname {} \;)
    apps=$(for app in $dirs; do basename "$app" | cut -d- -f1 ; done | \grep -Ev 'erl_interface|jinterface' | xargs echo)
    # shellcheck disable=SC2086
    "$1"/bin/dialyzer --output_plt "$plt" --build_plt --apps $apps >>"$build_log" 2>&1
    status=$?
    if [ $status -eq 0 ] || [ $status -eq 2 ]; then
        echo "Done building $plt"
        return 0
    else
        echo "Error building PLT, see $build_log for details"
        return 1
    fi
}

do_plt() {
    ACTIVE_PATH="$1"
    if [ -n "$ACTIVE_PATH" ]; then
        plt="$ACTIVE_PATH"/dialyzer/plt
        if [ -f "$plt" ]; then
            echo 'Dialyzer PLT for the active installation is:'
            echo "$plt"
            return 0
        else
            echo 'There is no Dialyzer PLT for the active installation'
            return 1
        fi
    else
        echo 'No Erlang/OTP installation is currently active'
        return 2
    fi
}

print_buildopts() {
    buildopts="$1/$KERL_CONFIG_STORAGE_FILENAME"
    if [ -f "$buildopts" ]; then
        echo 'The build options for the active installation are:'
        cat "$buildopts"
        return 0
    else
        echo 'The build options for the active installation are not available.'
        return 1
    fi
}

do_deploy() {
    if [ -z "$1" ]; then
        echo 'No host given'
        exit 1
    fi
    host="$1"

    assert_valid_installation "$2"
    rel="$(get_name_from_install_path "$2")"
    path="$2"
    remotepath="$path"

    if [ -n "$3" ]; then
        remotepath="$3"
    fi

    # shellcheck disable=SC2086
    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" true >/dev/null 2>&1; then
        echo "Couldn't ssh to $host"
        exit 1
    fi

    echo "Cloning Erlang/OTP $rel ($path) to $host ($remotepath) ..."

    # shellcheck disable=SC2086
    if ! rsync -aqz -e "ssh $KERL_DEPLOY_SSH_OPTIONS" $KERL_DEPLOY_RSYNC_OPTIONS "$path/" "$host:$remotepath/"; then
        echo "Couldn't rsync Erlang/OTP $rel ($path) to $host ($remotepath)"
        exit 1
    fi

    # shellcheck disable=SC2086,SC2029
    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" "cd \"$remotepath\" && env ERL_TOP=\"\$(pwd)\" ./Install $INSTALL_OPT \"\$(pwd)\" >/dev/null 2>&1"; then
        echo "Couldn't install Erlang/OTP $rel to $host ($remotepath)"
        exit 1
    fi

    # shellcheck disable=SC2086,SC2029
    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" "cd \"$remotepath\" && sed -i -e \"s#$path#\"\$(pwd)\"#g\" activate"; then
        echo "Couldn't completely install Erlang/OTP $rel to $host ($remotepath)"
        exit 1
    fi

    echo "On $host, you can activate this installation running the following command:"
    echo ". $remotepath/activate"
    echo 'Later on, you can leave the installation typing:'
    echo 'kerl_deactivate'
}


# Quoted from https://github.com/mkropat/sh-realpath
# LICENSE: MIT

realpath() {
    canonicalize_path "$(resolve_symlinks "$1")"
}

resolve_symlinks() {
    _resolve_symlinks "$1"
}

_resolve_symlinks() {
    _assert_no_path_cycles "$@" || return

    if path=$(readlink -- "$1"); then
        dir_context=$(dirname -- "$1")
        _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@"
    else
        printf '%s\n' "$1"
    fi
}

_prepend_dir_context_if_necessary() {
    if [ "$1" = . ]; then
        printf '%s\n' "$2"
    else
        _prepend_path_if_relative "$1" "$2"
    fi
}

_prepend_path_if_relative() {
    case "$2" in
        /* ) printf '%s\n' "$2" ;;
         * ) printf '%s\n' "$1/$2" ;;
    esac
}

_assert_no_path_cycles() {
    target=$1
    shift

    for path in "$@"; do
        if [ "$path" = "$target" ]; then
            return 1
        fi
    done
}

canonicalize_path() {
    if [ -d "$1" ]; then
        _canonicalize_dir_path "$1"
    else
        _canonicalize_file_path "$1"
    fi
}

_canonicalize_dir_path() {
    (cd "$1" 2>/dev/null && pwd -P)
}

_canonicalize_file_path() {
    dir=$(dirname -- "$1")
    file=$(basename -- "$1")
    (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}

# END QUOTE

is_valid_install_path() {
    # don't allow installs into .erlang because
    # it's a special configuration file location for OTP
    if [ "$(basename -- "$1")" = '.erlang' ]; then
        echo 'ERROR: You cannot install a build into .erlang. (It is a special configuration file location for OTP.)'
        return 1
    fi

    candidate=$(realpath "$1")
    canonical_home=$(realpath "$HOME")
    canonical_base_dir=$(realpath "$KERL_BASE_DIR")

    # don't allow installs into home directory
    if [ "$candidate" = "$canonical_home" ]; then
        echo "ERROR: You cannot install a build into $HOME. It's a really bad idea."
        return 1
    fi

    # don't install into our base directory either.
    if [ "$candidate" = "$canonical_base_dir" ]; then
        echo "ERROR: You cannot install a build into $KERL_BASE_DIR."
        return 1
    fi

    INSTALLED_NAME=$(get_name_from_install_path "$candidate")
    if [ -n "$INSTALLED_NAME" ]; then
        echo "ERROR: Installation ($INSTALLED_NAME) already registered for this location ($1)"
        return 1
    fi

    # if the install directory exists,
    # do not allow installs into a directory that is not empty
    if [ -e "$1" ]; then
        if [ ! -d "$1" ]; then
            echo "ERROR: $1 is not a directory."
            return 1
        else
            count=$(\find "$1" | wc -l)
            if [ "$count" -ne 1 ]; then
                echo "ERROR: $1 does not appear to be an empty directory."
                return 1
            fi
        fi
    fi

    return 0
}

maybe_remove() {
    candidate=$(realpath "$1")
    canonical_home=$(realpath "$HOME")

    if [ "$candidate" = "$canonical_home" ]; then
        echo "WARNING: You cannot remove an install from $HOME; it's your home directory."
        return 0
    fi

    ACTIVE_PATH="$(get_active_path)"
    if [ "$candidate" = "$ACTIVE_PATH" ]; then
	echo 'ERROR: You cannot delete the active installation. Deactivate it first.'
	exit 1
    fi

    rm -Rf "$1"
}

list_print() {
    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
        if [ "$(\wc -l "$KERL_BASE_DIR/otp_$1")" != '0' ]; then
            cat "$KERL_BASE_DIR/otp_$1"
            return 0
        fi
    fi
    echo "There are no $1 available"
}

list_add() {
    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
        while read -r l; do
            if [ "$l" = "$2" ]; then
                return 1
            fi
        done <"$KERL_BASE_DIR/otp_$1"
        echo "$2" >>"$KERL_BASE_DIR/otp_$1" || exit 1
    else
        echo "$2" >"$KERL_BASE_DIR/otp_$1" || exit 1
    fi
}

list_remove() {
    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
        sed $SED_OPT -i -e "/^.*$2$/d" "$KERL_BASE_DIR/otp_$1" || exit 1
    fi
}

list_has() {
    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
        \grep "$2" "$KERL_BASE_DIR/otp_$1" >/dev/null 2>&1 && return 0
    fi
    return 1
}

path_usage() {
    echo "usage: $0 path [<install_name>]"
}

list_usage() {
    echo "usage: $0 list <releases|builds|installations>"
}

delete_usage() {
    echo "usage: $0 delete <build|installation> <build_name or path>"
}

cleanup_usage() {
    echo "usage: $0 cleanup <build_name|all>"
}

update_usage() {
    echo "usage: $0 update releases"
}

get_active_path() {
    if [ -n "$_KERL_ACTIVE_DIR" ]; then
        echo "$_KERL_ACTIVE_DIR"
    fi
    return 0
}

get_name_from_install_path() {
    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
        \grep -m1 -E "$1$" "$KERL_BASE_DIR"/otp_installations | cut -d' ' -f1
    fi
    return 0
}

get_install_path_from_name() {
    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
        \grep -m1 -E "$1$" "$KERL_BASE_DIR"/otp_installations | cut -d' ' -f2
    fi
    return 0
}

do_active() {
    ACTIVE_PATH="$(get_active_path)"
    if [ -n "$ACTIVE_PATH" ]; then
        echo 'The current active installation is:'
        echo "$ACTIVE_PATH"
        return 0
    else
        echo 'No Erlang/OTP installation is currently active'
        return 1
    fi
}

make_filename() {
    release=$(get_otp_version "$1")
    if [ "$release" -ge 17 ]; then
        echo "OTP-$1"
    else
        echo "OTP_$1"
    fi
}

download() {
    mkdir -p "$KERL_DOWNLOAD_DIR" || exit 1
    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
        FILENAME=$(make_filename "$1")
        github_download "$1" "$FILENAME".tar.gz
    else
        FILENAME="otp_src_$1"
        tarball_download "$FILENAME".tar.gz
    fi
}

github_download() {
    tarball_file="$KERL_DOWNLOAD_DIR/$2"
    tarball_url="$OTP_GITHUB_URL/archive/$2"
    prebuilt_url="$OTP_GITHUB_URL/releases/download/OTP-$1/otp_src_$1.tar.gz"
    if curl --silent --location --fail -I $prebuilt_url > /dev/null; then
        tarball_url=$prebuilt_url
        unset KERL_USE_AUTOCONF
    fi
    # if the file doesn't exist or the file has no size
    if [ ! -s $tarball_file ]; then
        echo "Downloading $1 to $KERL_DOWNLOAD_DIR..."
        curl -f -L -o $tarball_file $tarball_url || exit 1
    else
        # If the downloaded tarball was corrupted due to interruption while
        # downloading.
        if ! gunzip -t $tarball_file 2>/dev/null; then
            echo "$tarball_file corrupted and redownloading..."
            rm -rf $tarball_file
            curl -f -L -o $tarball_file $tarball_url || exit 1
        fi
    fi
}

tarball_download() {
    if [ ! -s "$KERL_DOWNLOAD_DIR/$1" ]; then
        echo "Downloading $1 to $KERL_DOWNLOAD_DIR"
        curl -f -L -o "$KERL_DOWNLOAD_DIR/$1" "$ERLANG_DOWNLOAD_URL/$1" || exit 1
        update_checksum_file
    fi
    ensure_checksum_file
    echo 'Verifying archive checksum...'
    SUM="$($MD5SUM "$KERL_DOWNLOAD_DIR/$1" | cut -d' ' -f $MD5SUM_FIELD)"
    ORIG_SUM="$(\grep -F "$1" "$KERL_DOWNLOAD_DIR"/MD5 | cut -d' ' -f2)"
    if [ "$SUM" != "$ORIG_SUM" ]; then
        echo "Checksum error, check the files in $KERL_DOWNLOAD_DIR"
        exit 1
    fi
    echo "Checksum verified ($SUM)"
}

apply_solaris_networking_patch() {
    patch -p1 <<_END_PATCH
--- otp-a/erts/emulator/drivers/common/inet_drv.c
+++ otp-b/erts/emulator/drivers/common/inet_drv.c
@@ -4166,16 +4166,7 @@
 	    break;

 	case INET_IFOPT_HWADDR: {
-#ifdef SIOCGIFHWADDR
-	    if (ioctl(desc->s, SIOCGIFHWADDR, (char *)&ifreq) < 0)
-		break;
-	    buf_check(sptr, s_end, 1+2+IFHWADDRLEN);
-	    *sptr++ = INET_IFOPT_HWADDR;
-	    put_int16(IFHWADDRLEN, sptr); sptr += 2;
-	    /* raw memcpy (fix include autoconf later) */
-	    sys_memcpy(sptr, (char*)(&ifreq.ifr_hwaddr.sa_data), IFHWADDRLEN);
-	    sptr += IFHWADDRLEN;
-#elif defined(SIOCGENADDR)
+#if defined(SIOCGENADDR)
 	    if (ioctl(desc->s, SIOCGENADDR, (char *)&ifreq) < 0)
 		break;
 	    buf_check(sptr, s_end, 1+2+sizeof(ifreq.ifr_enaddr));
_END_PATCH
}

apply_darwin_compiler_patch() {
    patch -p0 <<_END_PATCH
--- erts/emulator/beam/beam_bp.c.orig	2011-10-03 13:12:07.000000000 -0500
+++ erts/emulator/beam/beam_bp.c	2013-10-04 13:42:03.000000000 -0500
@@ -496,7 +496,8 @@
 }

 /* bp_hash */
-ERTS_INLINE Uint bp_sched2ix() {
+#ifndef ERTS_DO_INCL_GLB_INLINE_FUNC_DEF
+ERTS_GLB_INLINE Uint bp_sched2ix() {
 #ifdef ERTS_SMP
     ErtsSchedulerData *esdp;
     esdp = erts_get_scheduler_data();
@@ -505,6 +506,7 @@
     return 0;
 #endif
 }
+#endif
 static void bp_hash_init(bp_time_hash_t *hash, Uint n) {
     Uint size = sizeof(bp_data_time_item_t)*n;
     Uint i;
--- erts/emulator/beam/beam_bp.h.orig	2011-10-03 13:12:07.000000000 -0500
+++ erts/emulator/beam/beam_bp.h	2013-10-04 13:42:08.000000000 -0500
@@ -144,7 +144,19 @@
 #define ErtsSmpBPUnlock(BDC)
 #endif

-ERTS_INLINE Uint bp_sched2ix(void);
+ERTS_GLB_INLINE Uint bp_sched2ix(void);
+
+#ifdef ERTS_DO_INCL_GLB_INLINE_FUNC_DEF
+ERTS_GLB_INLINE Uint bp_sched2ix() {
+#ifdef ERTS_SMP
+    ErtsSchedulerData *esdp;
+    esdp = erts_get_scheduler_data();
+    return esdp->no - 1;
+#else
+    return 0;
+#endif
+}
+#endif

 #ifdef ERTS_SMP
 #define bp_sched2ix_proc(p) ((p)->scheduler_data->no - 1)
_END_PATCH
}

# javadoc 8 includes always-enabled document linting which causes
# documentation builds to fail on older OTP releases.
apply_javadoc_linting_patch() {
    # The _END_PATCH token is quoted below to disable parameter substitution
    patch -p0 <<'_END_PATCH'
--- lib/jinterface/doc/src/Makefile.orig	2016-05-23 14:34:48.000000000 -0500
+++ lib/jinterface/doc/src/Makefile	2016-05-23 14:35:48.000000000 -0500
@@ -142,7 +142,7 @@
 	rm -f errs core *~

 jdoc:$(JAVA_SRC_FILES)
-	(cd ../../java_src;$(JAVADOC) -sourcepath . -d $(JAVADOC_DEST) \
+	(cd ../../java_src;$(JAVADOC) -Xdoclint:none -sourcepath . -d $(JAVADOC_DEST) \
 		-windowtitle $(JAVADOC_TITLE) $(JAVADOC_PKGS))

 man:
_END_PATCH
}

# perl 5.24 fatalizes the warning this causes
apply_r14_beam_makeops_patch() {
    patch -p0 <<'_END_PATCH'
--- erts/emulator/utils/beam_makeops.orig	2016-05-23 21:40:42.000000000 -0500
+++ erts/emulator/utils/beam_makeops	2016-05-23 21:41:08.000000000 -0500
@@ -1576,7 +1576,7 @@
 	if $min_window{$key} > $min_window;

     pop(@{$gen_transform{$key}})
-	if defined @{$gen_transform{$key}}; # Fail
+	if defined $gen_transform{$key}; # Fail
     my(@prefix) = (&make_op($comment), &make_op('', 'try_me_else', &tr_code_len(@code)));
     unshift(@code, @prefix);
     push(@{$gen_transform{$key}}, @code, &make_op('', 'fail'));
_END_PATCH
}

# https://github.com/erlang/otp/commit/21ca6d3a137034f19862db769a5b7f1c5528dbc4.diff
apply_r15_beam_makeops_patch() {
    patch -p1 <<'_END_PATCH'
--- a/erts/emulator/utils/beam_makeops
+++ b/erts/emulator/utils/beam_makeops
@@ -1711,7 +1711,7 @@ sub tr_gen_to {
     my $prev_last;
     $prev_last = pop(@{$gen_transform{$key}})
-	if defined @{$gen_transform{$key}}; # Fail
+	if defined $gen_transform{$key}; # Fail

     if ($prev_last && !is_instr($prev_last, 'fail')) {
 	error("Line $line: A previous transformation shadows '$orig_transform'");
_END_PATCH
}

#https://github.com/erlang/otp/commit/a64c4d806fa54848c35632114585ad82b98712e8.diff
apply_wx_ptr_patch() {
    patch -p1 <<'_END_PATCH'
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index 0d2da5d4a79..8118136d30e 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -666,7 +666,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
     throw wxe_badarg(index);
   }
   void * temp = memenv->ref2ptr[index];
-  if((index < memenv->next) && ((index == 0) || (temp > NULL)))
+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL)))
     return temp;
   else {
     throw wxe_badarg(index);
@@ -678,7 +678,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
   if(!memenv)
     throw wxe_badarg(index);
   void * temp = memenv->ref2ptr[index];
-  if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
+  if((index < memenv->next) && ((index == 0) || (temp != (void *) NULL))) {
     ptrMap::iterator it;
     it = ptr2ref.find(temp);
     if(it != ptr2ref.end()) {
_END_PATCH
}

apply_r16_wx_ptr_patch() {
    patch -p1 <<'_END_PATCH'
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index cc9bcc995..1b1912630 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -757,7 +757,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
     throw wxe_badarg(index);
   }
   void * temp = memenv->ref2ptr[index];
-  if((index < memenv->next) && ((index == 0) || (temp > NULL)))
+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL)))
     return temp;
   else {
     throw wxe_badarg(index);
@@ -769,7 +769,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
   if(!memenv)
     throw wxe_badarg(index);
   void * temp = memenv->ref2ptr[index];
-  if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL))) {
     ptrMap::iterator it;
     it = ptr2ref.find(temp);
     if(it != ptr2ref.end()) {
_END_PATCH
}

# https://github.com/erlang/otp/commit/e27119948fc6ab28bea81019720bddaac5b655a7.patch
apply_zlib_patch()
{

    patch -p1 <<'_END_PATCH'
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index 656de7c49ad..4491d486837 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -1193,6 +1193,7 @@ typedef struct B2TContext_t {
     } u;
 } B2TContext;

+static B2TContext* b2t_export_context(Process*, B2TContext* src);

 static uLongf binary2term_uncomp_size(byte* data, Sint size)
 {
@@ -1225,7 +1226,7 @@ static uLongf binary2term_uncomp_size(byte* data, Sint size)

 static ERTS_INLINE int
 binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
-		    B2TContext* ctx)
+		    B2TContext** ctxp, Process* p)
 {
     byte *bytes = data;
     Sint size = data_size;
@@ -1239,8 +1240,8 @@ binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
     size--;
     if (size < 5 || *bytes != COMPRESSED) {
 	state->extp = bytes;
-        if (ctx)
-	    ctx->state = B2TSizeInit;
+        if (ctxp)
+	    (*ctxp)->state = B2TSizeInit;
     }
     else  {
 	uLongf dest_len = (Uint32) get_int32(bytes+1);
@@ -1257,16 +1258,26 @@ binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
                 return -1;
 	    }
 	    state->extp = erts_alloc(ERTS_ALC_T_EXT_TERM_DATA, dest_len);
-            ctx->reds -= dest_len;
+            if (ctxp)
+                (*ctxp)->reds -= dest_len;
 	}
 	state->exttmp = 1;
-        if (ctx) {
+        if (ctxp) {
+            /*
+             * Start decompression by exporting trap context
+             * so we don't have to deal with deep-copying z_stream.
+             */
+            B2TContext* ctx = b2t_export_context(p, *ctxp);
+            ASSERT(state = &(*ctxp)->b2ts);
+            state = &ctx->b2ts;
+
 	    if (erl_zlib_inflate_start(&ctx->u.uc.stream, bytes, size) != Z_OK)
 		return -1;

 	    ctx->u.uc.dbytes = state->extp;
 	    ctx->u.uc.dleft = dest_len;
 	    ctx->state = B2TUncompressChunk;
+            *ctxp = ctx;
         }
 	else {
 	    uLongf dlen = dest_len;
@@ -1308,7 +1319,7 @@ erts_binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size
 {
     Sint res;

-    if (binary2term_prepare(state, data, data_size, NULL) < 0 ||
+    if (binary2term_prepare(state, data, data_size, NULL, NULL) < 0 ||
         (res=decoded_size(state->extp, state->extp + state->extsize, 0, NULL)) < 0) {

         if (state->exttmp)
@@ -1435,7 +1446,7 @@ static Eterm binary_to_term_int(Process* p, Uint32 flags, Eterm bin, Binary* con
             if (ctx->aligned_alloc) {
                 ctx->reds -= bin_size / 8;
             }
-            if (binary2term_prepare(&ctx->b2ts, bytes, bin_size, ctx) < 0) {
+            if (binary2term_prepare(&ctx->b2ts, bytes, bin_size, &ctx, p) < 0) {
 		ctx->state = B2TBadArg;
 	    }
             break;
_END_PATCH
}

# https://github.com/erlang/otp/commit/4b0467c.patch
apply_in6addr_test_patch()
{

    patch -p1 <<'_END_PATCH'
diff --git a/erts/configure.in b/erts/configure.in
index caa1ce568b..6ebb3d3a25 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -2191,6 +2191,7 @@ AC_CACHE_CHECK(
 		#include <sys/types.h>
 		#include <sys/socket.h>
 		#include <netinet/in.h>
+		#include <stdio.h>
 	    ]],
 	    [[printf("%d", in6addr_any.s6_addr[16]);]]
 	)],
@@ -2214,6 +2215,7 @@ AC_CACHE_CHECK(
 		#include <sys/types.h>
 		#include <sys/socket.h>
 		#include <netinet/in.h>
+		#include <stdio.h>
 	    ]],
 	    [[printf("%d", in6addr_loopback.s6_addr[16]);]]
 	)],
_END_PATCH
}

case "$1" in
    version)
        echo "$KERL_VERSION"
        exit 0
        ;;
    build)
        if [ "$2" = 'git' ]; then
            if [ $# -ne 5 ]; then
                echo "usage: $0 $1 $2 <git_url> <git_version> <build_name>"
                exit 1
            fi
            do_git_build "$3" "$4" "$5"
        else
            if [ $# -eq 2 ]; then
                do_normal_build "$2" "$2"
            elif [ $# -eq 3 ]; then
                do_normal_build "$2" "$3"
            else
                echo "usage: $0 $1 <release> <build_name>"
                exit 1
            fi
        fi
        ;;
    install)
        if [ $# -lt 2 ]; then
            echo "usage: $0 $1 <build_name> [directory]"
            exit 1
        fi
        if [ $# -eq 3 ]; then
            do_install "$2" "$3"
        else
            if [ -z "$KERL_DEFAULT_INSTALL_DIR" ]; then
                do_install "$2" "$PWD"
            else
                do_install "$2" "$KERL_DEFAULT_INSTALL_DIR/$2"
            fi
        fi
        ;;
    install-docsh)
        ACTIVE_PATH="$(get_active_path)"
        if [ -n "$ACTIVE_PATH" ]; then
            ACTIVE_NAME="$(get_name_from_install_path "$ACTIVE_PATH")"
            if [ -z "$ACTIVE_NAME" ]; then
                ## TODO: Are git builds installed the usual way
                ##       or do we need this clause to provide a fallback?
                #BUILDNAME="$(basename "$ACTIVE_PATH")"
                echo "$ACTIVE_PATH is not a kerl installation"
                exit 1
            else
                BUILDNAME="$ACTIVE_NAME"
            fi
            install_docsh "$BUILDNAME" "$ACTIVE_PATH"
            echo 'Please kerl_deactivate and activate again to enable docsh'
        else
            echo 'No Erlang/OTP installation is currently active - cannot install docsh'
            exit 1
        fi
        ;;
    deploy)
        if [ $# -lt 2 ]; then
            echo "usage: $0 $1 <[user@]host> [directory] [remote_directory]"
            exit 1
        fi
        if [ $# -eq 4 ]; then
            do_deploy "$2" "$3" "$4"
        else
            if [ $# -eq 3 ]; then
                do_deploy "$2" "$3"
            else
                do_deploy "$2" .
            fi
        fi
        ;;
    update)
        if [ $# -lt 2 ]; then
            update_usage
            exit 1
        fi
        case "$2" in
            releases)
                rm -f "${KERL_BASE_DIR:?}"/otp_releases
                check_releases
                echo 'The available releases are:'
                list_print releases
                ;;
            *)
                update_usage
                exit 1
                ;;
        esac
        ;;
    list)
        if [ $# -ne 2 ]; then
            list_usage
            exit 1
        fi
        case "$2" in
            releases)
                check_releases
                list_print "$2"
                echo "Run '$0 update releases' to update this list from erlang.org"
                ;;
            builds)
                list_print "$2"
                ;;
            installations)
                list_print "$2"
                ;;
            *)
                echo "Cannot list $2"
                list_usage
                exit 1
                ;;
        esac
        ;;
    path)
        # Usage:
        # kerl path
        # # Print currently active installation path, else non-zero exit
        # kerl path <install>
        # Print path to installation with name <install>, else non-zero exit
        if [ -z "$2" ]; then
            activepath=$(get_active_path)
            if [ -z "$activepath" ]; then
                echo 'No active kerl-managed erlang installation'
                exit 1
            fi
            echo "$activepath"
        else
            # There are some possible extensions to this we could
            # consider, such as:
            # - if 2+ matches: prefer one in a subdir from $PWD
            # - prefer $KERL_DEFAULT_INSTALL_DIR
            match=
            for ins in $(list_print installations | cut -d' ' -f2); do
                if [ "$(basename "$ins")" = "$2" ]; then
                    if [ -z "$match" ]; then
                        match="$ins"
                    else
                        echo 'Error: too many matching installations' >&2
                        exit 2
                    fi
                fi
            done
            [ -n "$match" ] && echo "$match" && exit 0
            echo 'Error: no matching installation found' >&2 && exit 1
        fi
        ;;
    delete)
        if [ $# -ne 3 ]; then
            delete_usage
            exit 1
        fi
        case "$2" in
            build)
                rel="$(get_release_from_name "$3")"
                if [ -d "${KERL_BUILD_DIR:?}/$3" ]; then
                    maybe_remove "${KERL_BUILD_DIR:?}/$3"
                else
                    if [ -z "$rel" ]; then
                      echo "No build named $3"
                      exit 1
                    fi
                fi
                list_remove "$2"s "$rel,$3"
                echo "The $3 build has been deleted"
                ;;
            installation)
                assert_valid_installation "$3"
                if [ -d "$3" ]; then
                    maybe_remove "$3"
                else
                    maybe_remove "$(get_install_path_from_name "$3")"
                fi
                escaped="$(echo "$3" | \sed $SED_OPT -e 's#/$##' -e 's#\/#\\\/#g')"
                list_remove "$2"s "$escaped"
                echo "The installation \"$3\" has been deleted"
                ;;
            *)
                echo "Cannot delete $2"
                delete_usage
                exit 1
                ;;
        esac
        ;;
    active)
        if ! do_active; then
            exit 1;
        fi
        ;;
    plt)
        ACTIVE_PATH=$(get_active_path)
        if ! do_plt "$ACTIVE_PATH"; then
            exit 1;
        fi
        ;;
    status)
        echo 'Available builds:'
        list_print builds
        echo '----------'
        echo 'Available installations:'
        list_print installations
        echo '----------'
        if do_active; then
            ACTIVE_PATH=$(get_active_path)
            if [ -n "$ACTIVE_PATH" ]; then
                do_plt "$ACTIVE_PATH"
                print_buildopts "$ACTIVE_PATH"
            else
                echo 'No Erlang/OTP installation is currently active'
                exit 1
            fi
        fi
        exit 0
        ;;
    prompt)
        FMT=' (%s)'
        if [ -n "$2" ]; then
            FMT="$2"
        fi
        ACTIVE_PATH="$(get_active_path)"
        if [ -n "$ACTIVE_PATH" ]; then
            ACTIVE_NAME="$(get_name_from_install_path "$ACTIVE_PATH")"
            if [ -z "$ACTIVE_NAME" ]; then
                VALUE="$(basename "$ACTIVE_PATH")*"
            else
                VALUE="$ACTIVE_NAME"
            fi
            # shellcheck disable=SC2059
            printf "$FMT" "$VALUE"
        fi
        exit 0
        ;;
    cleanup)
        if [ $# -ne 2 ]; then
            cleanup_usage
            exit 1
        fi
        case "$2" in
            all)
                echo 'Cleaning up compilation products for ALL builds'
                rm -rf "${KERL_BUILD_DIR:?}"/*
                rm -rf "${KERL_DOWNLOAD_DIR:?}"/*
                rm -rf "${KERL_GIT_DIR:?}"/*
                echo "Cleaned up all compilation products under $KERL_BUILD_DIR"
                ;;
            *)
                echo "Cleaning up compilation products for $3"
                rm -rf "${KERL_BUILD_DIR:?}/$3"
                echo "Cleaned up compilation products for $3 under $KERL_BUILD_DIR"
                ;;
        esac
        ;;
    *)
        echo "unknown command: $1"; usage; exit 1
        ;;
esac
